package engine;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;

/**
 * Event scheduler listener.
 *
 * <p>Prelude: most agent-based simulation use a time-step tick-based update
 * scheme. We adopt an event-based scheduler which triggers an event after a
 * given time. The concept is based on Zaiyi Guo et al, Multi-timescale
 * event-scheduling in multi-agent immune simulation models, 2007.</p>
 *
 * <p>It works by having agents and other objects (like the user interface)
 * schedulling events to be dispatched after a given time. The time units are
 * days. They are not translated into real time units; they are processed as
 * soon as possible by their time order. In order to always keep events in order
 * we use internally {@link SortedMap}, which uses a red-black tree, which is a
 * type of a binary search tree.</p>
 *
 * @author Ricardo Cruz {@literal <ricardo.pdm.cruz@gmail.com>}
 * @since  2014-11-11
 */
public class Scheduler
{
	private static class SortedMultiMap extends TreeMap<Double, List<Event>> {};

	private static final Scheduler singleton = new Scheduler();
	private SortedMultiMap events = new SortedMultiMap();
	private double clock = 0;
	private Random random = new Random();

	private static class Event {
		public SchedulerListener user;
		public Object userEvent;
		public Event(SchedulerListener user, Object userEvent) {
			this.user = user;
			this.userEvent = userEvent;
		}
	}

	/**
	 * Schedule an event.
	 * @param time delay
	 * @param listener the object for which to notify the event
	 * @param event the event message defined by the user
	 */
	public static void post(double time, SchedulerListener listener, Object event) {
		double timestamp = time + singleton.clock;
		List<Event> events = singleton.events.get(timestamp);
		if(events == null) {
			events = new LinkedList<Event>();
			singleton.events.put(new Double(timestamp), events);
		}
		events.add(new Event(listener, event));
	}

	/**
	 * Schedule an event, using a normal distribution for time.
	 * @param time delay
	 * @param time_dev deviation for time delay
	 * @param listener the object for which to notify the event
	 * @param event the event message defined by the user
	 */
	public static void post(double time, double time_dev, SchedulerListener listener, Object event) {
		post((singleton.random.nextGaussian()*time_dev)+time, listener, event);
	}

	/**
	 * The virtual time spent (in days).
	 * @return the time spent so far (in days)
	 */
	public static double clock() {
		return singleton.clock;
	}

	/**
	 * Operated by {@Simulation}.
	 */
	public static void reset() {
		singleton.events.clear();
		singleton.clock = 0;
	}

	/**
	 * Internal usage. Processes the next time-event in queue. May process more
	 * than one event since several events may coincidentally be associated to
	 * the same time-event.
	 */
	public static void process() {
		Map.Entry<Double,List<Event>> entry = singleton.events.pollFirstEntry();
		if(entry != null) {
			singleton.clock = entry.getKey();
			for(Event ev: entry.getValue())
				ev.user.event(ev.userEvent);
		}
	}

	private Scheduler() {} // singleton
}
